package updown.service.updown;

import com.alibaba.fastjson.JSONObject;
import com.netflix.appinfo.AmazonInfo;
import com.netflix.appinfo.DataCenterInfo;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.discovery.shared.Application;
import com.netflix.discovery.shared.Applications;
import com.netflix.discovery.shared.Pair;
import com.netflix.discovery.shared.transport.EurekaHttpResponse;
import com.netflix.eureka.transport.JerseyReplicationClient;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.netflix.eureka.EurekaServiceInstance;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import updown.config.MyEurekaServerConfig;
import updown.constant.MsgConstant;
import updown.constant.MyInstanceConstant;
import updown.domain.MyInstanceInfo;
import updown.domain.TaskInfo;
import updown.util.SleepUtil;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;


/**
 * <p>Title: com.damai.downupservice.serviceInfo </p>
 * <p>Description:updown: - eureka 服务列表 </p>
 *
 * @Author:jiangkaixin
 * @Date:2021/11/5
 * @Version: 1.0
 */
@Slf4j
@Service
public class EurekaService {

    @Autowired
    private JerseyReplicationClient client;

    @Autowired
    private MyEurekaServerConfig serverConfig;

    @Autowired
    private DiscoveryClient discoveryClient;

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 直接发送给 eureka Server 获取对应的 instanceId 服务 最新的状态信息
     *
     * @param serviceName
     * @param instanceId
     * @return
     */
    public MyInstanceInfo getSpecificInstanceByNameAndInstanceId(String serviceName, String instanceId) {
        String url = serverConfig.getServiceUrl() + "apps/" + serviceName + "/" + instanceId;
        MyInstanceInfo instanceInfo = getInstanceInfoFromEurekaNoCache(url);
        return instanceInfo;
    }

    /**
     * 发送请求 获取 服务实例信息 从注册表中直接获取，而不是从缓存
     * @param url
     * @return
     */
    private MyInstanceInfo getInstanceInfoFromEurekaNoCache(String url) {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet(url);
        httpGet.addHeader("Accept", "application/json");
        MyInstanceInfo instanceInfo = null;
        CloseableHttpResponse response = null;
        try {
            response = httpClient.execute(httpGet);
            org.apache.http.HttpEntity entity = response.getEntity();
            InputStream inputStream = entity.getContent();
            byte[] bytes = new byte[4096];
            int read = IOUtils.read(inputStream, bytes);
            if (read > 0) {
                byte[] b = new byte[read];
                for (int i = 0; i < read; i++) {
                    b[i] = bytes[i];
                }
                String s = new String(b);
                log.info("从eureka 中获取的 instance 信息:{}", s);
                instanceInfo = JSONObject.parseObject(s, MyInstanceInfo.class);
                log.info("解析成 MyInstanceInfo:{}", instanceInfo);
            }
        } catch (IOException e) {
            log.info("getApache error:{}", e);
        } finally {
            try {
                response.close();
            } catch (IOException e) {
                log.info("getApache finally error:{}", e);
            }
        }
        return instanceInfo;
    }

    /**
     * 从eureka 中 过滤出 服务实例信息
     *
     * @param serviceName
     * @return
     */
    public List<InstanceInfo> getInstancesByNameFromEureka(String serviceName) {
        EurekaHttpResponse<Applications> response = client.getApplications(null);
        Applications applications = response.getEntity();
        for (Application app : applications.getRegisteredApplications()) {
            if (serviceName.equalsIgnoreCase(app.getName())) {
                log.info("从 eureka 中找到了 instanceInfos 实例信息:{}", app.getInstances());
                return app.getInstances();
            }
        }
        log.info("{}:在eureka 中未找到服务实例信息", serviceName);
        return null;
    }

    /**
     * 从 eureka service 中拉取服务信息用于展示
     *
     * @param model
     */
    public void servicesInEurekaServer(Map<String, Object> model) {
        EurekaHttpResponse<Applications> response = client.getApplications(null);
        Applications applications = response.getEntity();
        log.info("servicesInEurekaServer - applications:{}", applications);
        ArrayList<Map<String, Object>> apps = new ArrayList<>();
        for (Application app : applications.getRegisteredApplications()) {
            LinkedHashMap<String, Object> appData = new LinkedHashMap<>();
            apps.add(appData);
            appData.put("name", app.getName());
            Map<String, Integer> amiCounts = new HashMap<>();
            Map<InstanceInfo.InstanceStatus, List<Pair<String, String>>> instancesByStatus = new HashMap<>();
            Map<String, Integer> zoneCounts = new HashMap<>();
            for (InstanceInfo info : app.getInstances()) {
                String id = info.getId();
                String url = info.getStatusPageUrl();
                InstanceInfo.InstanceStatus status = info.getStatus();
                String ami = "n/a";
                String zone = "";
                if (info.getDataCenterInfo().getName() == DataCenterInfo.Name.Amazon) {
                    AmazonInfo dcInfo = (AmazonInfo) info.getDataCenterInfo();
                    ami = dcInfo.get(AmazonInfo.MetaDataKey.amiId);
                    zone = dcInfo.get(AmazonInfo.MetaDataKey.availabilityZone);
                }
                Integer count = amiCounts.get(ami);
                if (count != null) {
                    amiCounts.put(ami, count + 1);
                } else {
                    amiCounts.put(ami, 1);
                }
                count = zoneCounts.get(zone);
                if (count != null) {
                    zoneCounts.put(zone, count + 1);
                } else {
                    zoneCounts.put(zone, 1);
                }
                List<Pair<String, String>> list = instancesByStatus
                        .computeIfAbsent(status, k -> new ArrayList<>());
                list.add(new Pair<>(id, url));
            }
            appData.put("amiCounts", amiCounts.entrySet());
            appData.put("zoneCounts", zoneCounts.entrySet());
            ArrayList<Map<String, Object>> instanceInfos = new ArrayList<>();
            appData.put("instanceInfos", instanceInfos);
            for (Map.Entry<InstanceInfo.InstanceStatus, List<Pair<String, String>>> entry : instancesByStatus
                    .entrySet()) {
                List<Pair<String, String>> value = entry.getValue();
                InstanceInfo.InstanceStatus status = entry.getKey();
                LinkedHashMap<String, Object> instanceData = new LinkedHashMap<>();
                instanceInfos.add(instanceData);
                instanceData.put("status", entry.getKey());
                ArrayList<Map<String, Object>> instances = new ArrayList<>();
                instanceData.put("instances", instances);
                instanceData.put("isNotUp", status != InstanceInfo.InstanceStatus.UP);

                // TODO

                /*
                 * if(status != InstanceInfo.InstanceStatus.UP){
                 * buf.append("<font color=red size=+1><b>"); }
                 * buf.append("<b>").append(status
                 * .name()).append("</b> (").append(value.size()).append(") - ");
                 * if(status != InstanceInfo.InstanceStatus.UP){
                 * buf.append("</font></b>"); }
                 */

                for (Pair<String, String> p : value) {
                    LinkedHashMap<String, Object> instance = new LinkedHashMap<>();
                    instances.add(instance);
                    instance.put("id", p.first());
                    String url = p.second();
                    instance.put("url", url);
                    boolean isHref = url != null && url.startsWith("http");
                    instance.put("isHref", isHref);
                    /*
                     * String id = p.first(); String url = p.second(); if(url != null &&
                     * url.startsWith("http")){
                     * buf.append("<a href=\"").append(url).append("\">"); }else { url =
                     * null; } buf.append(id); if(url != null){ buf.append("</a>"); }
                     * buf.append(", ");
                     */
                }
            }
            // out.println("<td>" + buf.toString() + "</td></tr>");
        }
        for (Map<String, Object> app : apps) {
            log.info("获取到的 app 信息:{}", app);
        }
        model.put("apps", apps);
    }


    /**
     * 发送请求，之后 等待 1s 再次检查状态
     *
     * @param instanceId
     * @param serviceName
     * @param down
     * @return
     */
    public Boolean sendRequestAndWaitCheckStatus(String instanceId, String serviceName, String status) {
        sendRequest(instanceId, serviceName, status);
        log.info("发送 状态更改请求成功,instanceId:{},serviceName{},status:{}", instanceId, serviceName, status);
        SleepUtil.sleepSeconds(1);
        Boolean sameStatus = isSameStatus(serviceName, instanceId, status);
        log.info("再次检查 是否状态相同:{}", sameStatus);
        return sameStatus;
    }


    /**
     * 下线请求
     *
     * @param instanceId
     * @param serviceName
     * @param status
     * @return
     */
    private String sendRequest(String instanceId, String serviceName, String status) {
        StringBuffer url = new StringBuffer(serverConfig.getServiceUrl() + "apps/");
        // http://localhost:8761/eureka/apps/FEIGN-PROVIDER/DESKTOP-HGTNR01:feign-provider:8001/status?value=DOWN
        url.append(serviceName).append("/").append(instanceId).append("/status?value=").append(status);
        log.info("下线请求 sendRequest, url=:{}", url);
        restTemplate.put(url.toString(), null);
        return instanceId;
    }


    /**
     * 查询是否 是指定的 状态
     *
     * @param serviceName
     * @param currentInstanceId
     * @param status
     * @return
     */
    public Boolean isSameStatus(String serviceName, String currentInstanceId, String status) {
        MyInstanceInfo myInstanceInfo = getSpecificInstanceByNameAndInstanceId(serviceName, currentInstanceId);
        Boolean same = myInstanceInfo == null ? Boolean.FALSE : (status.equals(myInstanceInfo.getInstanceOverriddenStatus()) ? Boolean.TRUE : Boolean.FALSE);
        log.info("isSameStatus :{} , same:{}, 服务器中的 overrideStatus:{}", myInstanceInfo, same, myInstanceInfo.getInstanceOverriddenStatus());
        return same;
    }

    /**
     * 是否重启成功 增加重试次数
     *
     * @param serviceName
     * @param currentInstanceId
     * @param retryTimes
     * @return
     */
    public boolean isRestartSuccess(String serviceName, String currentInstanceId, int retryTimes) {
        MyInstanceInfo myInstanceInfo = getSpecificInstanceByNameAndInstanceId(serviceName, currentInstanceId);
        if (myInstanceInfo == null || !MyInstanceConstant.UP.equals(myInstanceInfo.getInstanceStatus())) {
            while (retryTimes > 0) {
                if (!isRestartSuccess(serviceName, currentInstanceId)) {
                    return true;
                }
                retryTimes--;
            }
        }
        return myInstanceInfo == null ? false : MyInstanceConstant.UP.equals(myInstanceInfo.getInstanceStatus()) ? true : false;
    }

    /**
     * 是否重启成功
     *
     * @param serviceName
     * @param currentInstanceId
     * @return
     */
    public boolean isRestartSuccess(String serviceName, String currentInstanceId) {
        MyInstanceInfo myInstanceInfo = getSpecificInstanceByNameAndInstanceId(serviceName, currentInstanceId);
        if (myInstanceInfo == null || !MyInstanceConstant.UP.equals(myInstanceInfo.getInstanceStatus())) {
        }
        return myInstanceInfo == null ? false : MyInstanceConstant.UP.equals(myInstanceInfo.getInstanceStatus()) ? true : false;
    }

    /**
     * 检查本地的表中，对应的 instanceId 服务的信息，确保本地已经重新发现了改服务， 代表最终的 重新启动成功
     *
     * @param serviceName
     * @param currentInstanceId
     * @return
     */
    public boolean checkLocalRegistryHasInstanceInfo(String serviceName, String currentInstanceId) {
        log.info("检查本地是否发现了 重新注册的服务(),serviceName:{},currentInstanceId:{}", serviceName, currentInstanceId);
        List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
        log.info("本地查询到的 instance 列表是:{}", instances);
        if (instances == null || instances.isEmpty()) {
            log.info("本地查询到的 instance 列表，未找到");
            return false;
        }

        for (ServiceInstance instance : instances) {
            EurekaServiceInstance eurekaInstance = (EurekaServiceInstance) instance;
            log.info("强转成 eurekaInstance 之后:{}", eurekaInstance);
            InstanceInfo instanceInfo = eurekaInstance.getInstanceInfo();
            log.info("instanceInfo :{}", instanceInfo);
            if (currentInstanceId.equalsIgnoreCase(instanceInfo.getInstanceId())) {
                log.info("instanceId 一致");
                InstanceInfo.InstanceStatus status = instanceInfo.getStatus();
                String statusStr = status.toString();
                if (MyInstanceConstant.UP.equalsIgnoreCase(statusStr)) {
                    log.info("状态为 UP 本地发现 up状态，重新启动的服务， 返回成功");
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * 发送下线请求
     * @param instanceId
     * @param serviceName
     * @return
     */
    public TaskInfo sendDown(String instanceId, String serviceName) {
        if (sendRequestAndWaitCheckStatus(instanceId, serviceName, "DOWN")) {
            return TaskInfo.builder().taskDes("InstanceId:" + instanceId + "," + MsgConstant.send_down_msg).build();
        }
        return TaskInfo.builder().taskDes("InstanceId:" + instanceId + "," + MsgConstant.send_down_msg).success(false).build();
    }
}
